program gekai;

{*
Main program of project gekai
}

{$IFNDEF FPC}
{$APPTYPE CONSOLE}
{$ENDIF}

uses 
  sysutils, gl, glu, glut, opengls, md3s, keyboards, fonts, fpscounters, 
  grounds, cameras, VUMaths, skyboxes, humanoids, Math, glSOARHelpers, 
  glSOARMain, vectors, debugs, trikes, models, textures, billboards;

const
  TREE_COUNT = 2000;
  GRASS_COUNT = 5000;
  GRASS_BASE = 100;
  GRASS_THRESHOLD = 10;

var 
  Player : THumanoid;
  Trike : TTrike;
  SkyBox : TSkyBox;
  Oak1 : TModel;
  Trees : TBillboardList;
  Grass : TBillboardList;
  GrassOldX : real = -1;
  GrassOldY : real = -1;
  ShowTrees : boolean = false;
  tr : single;

procedure InitData;
var i : integer;
begin
  for i := 1 to ParamCount-1 do
    if ParamStr(i) = '-cd' then
      ChDir(ParamStr(i+1));
end;

procedure InitFont;
begin
  Font := TFont.Create('data/font/dejavu_mono.png');
end;

procedure InitSky;
begin
  //SkyBox := TSkyBox.Create('test',2000);
  //SkyBox := TSkyBox.Create('space',2000);
  //SkyBox := TSkyBox.Create('desert',2000);
  SkyBox := TSkyBox.Create('forest',2000);
end;

procedure InitGround;
begin
  ReturnMouse;
  //Ground.Initialize('data/','desert');
  Ground.Initialize('data/','forest');
  Ground.Position := Vector3(80.26, 296.94, 163.78);
  Ground.Direction := Vector3(1.27, -0.09, -0.23);
  Ground.Grab := true;
  // randomize trees
  writeln('Initializing ',TREE_COUNT,' tree billboards');
  Trees := TBillboardList.Create;
  Trees.Randomize(TREE_COUNT,0,0,Ground.Size,Ground.Size,
    ['data/model/plant/oak/oak1b.png',
    'data/model/plant/oak/oak2b.png',
    'data/model/plant/oak/oak3b.png',
    'data/model/plant/oak/oak4b.png'],
    30,30);
  // randomize grass
  Grass := TBillboardList.Create;
  Grass.Randomize(GRASS_COUNT,100,250,200,350,['data/model/plant/grass/grass1b.png'],5,5);
  // done
  writeln('  ok');
end;

procedure InitPlayer;
begin
  Player := THumanoid.Create('data/model/human/male/','desert');
  Player.RightWeapon := Player.CreateEquipment(Player.Upper, 'tag_right_hand',  'tag_handle', 'data/model/weapon/sword/', 'basic',    'default', 'IDLE');
  Player.LeftWeapon :=  Player.CreateEquipment(Player.Lower, 'tag_left_case',   'tag_handle', 'data/model/weapon/sword/', 'claymore', 'default', 'IDLE');
  Player.Shield :=      Player.CreateEquipment(Player.Upper, 'tag_left_shield', 'tag_handle', 'data/model/shield/round/', 'round',    'default', 'IDLE');
  Player.CalculateEyesMatrix;
  Player.Position := Vector3D(112.77, 494.60, 156.00);
  Player.Rotation := Vector3D(0, 0, 0);
  // trike
  Trike := TTrike.Create('data/model/vehicle/trike/','default');
  Trike.Position := Vector3D(112.77-5, 494.60, 156.00);
  Trike.Rotation := Vector3D(0, 0, 0);
  // oak1
  Oak1 := TModel.Create;
  Oak1.BaseModel := TMd3ModelDynamic.Create;
  Oak1.BaseModel.ModelStatic := Md3ModelStaticList.Load('data/model/plant/oak/', 'oak1', 'default');
  Oak1.Scale := 0.08; 
//  Oak1.BaseModel.ModelStatic := Md3ModelStaticList.Load('data/model/plant/comictree/', 'tree1', 'default'); 
//  Oak1.BaseModel.ModelStatic := Md3ModelStaticList.Load('data/model/plant/comictree/', 'tree1fan', 'default'); 
  //Trike.LinkModel('tag_wheel',Front,'tag_wheel');
  Oak1.BaseModel.Base('tag_ground');
  Oak1.BaseModel.SetAnimation('IDLE');   
end;

procedure ShowTreeBillboards;
{*
Show tree billboards - distant trees
}
var i : integer;
begin
  glDisable(GL_LIGHTING);
  glEnable(GL_BLEND);
  for i := 0 to Trees.Count-20 do
  begin
    glPushMatrix;
    // progresive hiding for billboards soon to be hiden
    tr := 1;
    if i >= Trees.Count-40 then
      tr := 1-(i - (Trees.Count-40)) / 20;
    // hue
    glColor4f(TBillboard(Trees.Items[i]).Hue,TBillboard(Trees.Items[i]).Hue,TBillboard(Trees.Items[i]).Hue,tr);
    // show tree billboard
    TBillboard(Trees.Items[i]).Show;
    glPopMatrix;
  end; 
end;

procedure ShowGrassBillboards;
{*
Show tree billboards - distant trees
}
var i : integer;
    d : real;
begin
  glDisable(GL_CULL_FACE);
  glDisable(GL_LIGHTING);
  glEnable(GL_BLEND);
  for i := 0 to Grass.Count-1 do
  begin
    glPushMatrix;
    // distance based alpha hiding 
    tr := 1;
    with TBillboard(Grass.Items[i]) do
      d := sqrt(sqr(Position.X - Ground.Position.X)+sqr(Position.Y - Ground.Position.Y));
    if d > 25 then
      tr := 1 - (d-25) / 25;
//    if d > GRASS_BASE / 2 then
//      tr := 1 - (d-GRASS_BASE/2) / (GRASS_BASE/2);
    // hue
    glColor4f(TBillboard(Grass.Items[i]).Hue,TBillboard(Grass.Items[i]).Hue,TBillboard(Grass.Items[i]).Hue,tr);
    // show tree billboard
    TBillboard(Grass.Items[i]).Show(true);
    glPopMatrix;
  end; 
end;

procedure ShowTreeModels;
{*
Show tree models - near trees
}
var i : integer;
begin
  for i := Trees.Count-40 to Trees.Count-1 do
  begin
    glColor4f(1,1,1,1);		
    //  glBlendFunc(GL_SRC_ALPHA,GL_ONE);
    //  glEnable(GL_BLEND);
    //  glDisable(GL_DEPTH_TEST);

    glPushMatrix;
    with TBillboard(Trees.Items[i]) do
    begin      
      // glTranslateF(Position.X,Position.Y,Position.Z);
      Oak1.Position.X := Position.X;
      Oak1.Position.Y := Position.Y;
      Oak1.Position.Z := Position.Z;
      Oak1.Rotation.Z := RotationFixed.Z; //(180/pi)*( Atan2(Position.X - Ground.Position.X,Position.Y - Ground.Position.Y) ) - 90;
      Oak1.Draw(Fps.Time/1000);
    end;
    glPopMatrix;
  end;  
end;

procedure OnDisplay; cdecl;
begin
  // splashscreen
  if Splash then
  begin
    case SplashProgress of
      1: InitData;
      2: InitFont;
      3: InitSky;
      4: InitGround;
      5: InitPlayer;
    end;
    exit;
  end;

  // handle semi-correct termination of glutMainLoop 
  if Terminating then
    exit;

  // OpenGL soap
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;

  // keep player on ground
  Player.Position.Z := GetTerrainZ(Player.Position.X, Player.Position.Y, Player.Position.Z);

  // player rotation
  // in first peson view use mouse for rotation
  if Ground.CameraMode = cmFirstPerson then
  begin
    Player.Rotation.Z := 180*Ground.GrabGroundRotation/Pi;
    Player.Strafe(KeyIsDown(vkSmallA),KeyIsDown(vkSmallD));
  end else 
  begin
    if KeyIsDown(vkSmallA) then Player.Rotation.Z := Player.Rotation.Z + 5;
    if KeyIsDown(vkSmallD) then Player.Rotation.Z := Player.Rotation.Z - 5;
  end;
  
  // player movement W/S
  Player.Move(KeyIsDown(vkSmallW),KeyIsDown(vkSmallS));

  // exit
  if KeyIsDown(vkEsc) then
    raise ExGlutTerminate.Create('ESC Pressed');
  // debug position and direction
  if KeyIsDown(vkF10) then
    writeln('Ground: Position=',Vector3ToStr(Ground.Position),' Direction=',Vector3ToStr(Ground.Direction));

  // start crouch
  if KeyIsDown(vkSmallC) and not Player.Crouch then
  begin
    Player.Lower.SetAnimation('CROUCH');
    Player.CalculateEyesMatrix; // FIXME: this will not work because it calculate eyes matrix from first frame, not from crouch
    Player.Crouch := true;
  end;
  // end crouch
  if not KeyIsDown(vkSmallC) and Player.Crouch then
  begin
    Player.Lower.SetAnimation('IDLE');
    Player.CalculateEyesMatrix; // FIXME: this will not work because it calculate eyes matrix from first frame, not from crouch
    Player.Crouch := false;
  end;  
  
  // tree displaying on/off
  if KeyTrigger(vkF7) then
    ShowTrees := not ShowTrees;

  // automatic tree sorting every seconds or so  
  Trees.SortBy(trunc(Ground.Position.X),trunc(Ground.Position.Y),200);
  // automatic grass sorting every seconds or so  
  Grass.SortBy(trunc(Ground.Position.X),trunc(Ground.Position.Y),200);
  

  // forced grass randomization
  if KeyTrigger(vkF8) then
  begin  
    writeln('>>> grass randomization');
    Grass.ReSquare(GrassOldX, GrassOldY, Ground.Position.X, Ground.Position.Y, GRASS_BASE, GRASS_THRESHOLD, true);
  end;

  // grass placement
  if Grass.ReSquare(GrassOldX, GrassOldY, Ground.Position.X, Ground.Position.Y, GRASS_BASE, GRASS_THRESHOLD, false) then
  begin
    writeln(format('>>> grass ReSquare %1.2f %1.2f %1.2f %1.2f ',[GrassOldX, GrassOldY, Ground.Position.X, Ground.Position.Y]));
    GrassOldX := Ground.Position.X;
    GrassOldY := Ground.Position.Y;
  end;

{
  // w = walk
  if (KeyIsDown(vkSmallW)) then
  begin
    Player.Lower.SetAnimation('WALK');
    Player.Upper.SetAnimation('WALK');
    Player.Weapon.SetAnimation('IDLE');
  end;
  // s = slash
  if (KeyIsDown(vkSmallS)) then
  begin
    Player.Lower.SetAnimation('IDLE');
    Player.Upper.SetAnimation('ATTACK2');
    Player.Weapon.SetAnimation('IDLE');
  end;
}

  // ground
  Ground.UpdateCamera(
    Vector3(Player.Position.X, Player.Position.Y, Player.Position.Z),
    Vector3(Player.Rotation.X, Player.Rotation.Y, Player.Rotation.Z),
    Player.EyesM);
  Ground.UpdateGrab;
  Ground.Draw;

  // skybox
  glPushMatrix;
  glTranslatef(ground.position.x, ground.position.y, ground.position.z);
  glRotatef(90, 1, 0, 0);
  SkyBox.Show;
  glPopMatrix;

  UseLights;
  Ground.UpdateFog;
  

  // player
  glPushMatrix;
  Player.Draw(Fps.Time/1000);
  glPopMatrix;

  // trike stearing IJKL
  if KeyIsDown(vkSmallJ) then
  begin
    Trike.Front.BaseAngle := Trike.Front.BaseAngle + 5;
    if Trike.Front.BaseAngle > 50 then Trike.Front.BaseAngle := 50;
  end else 
  if KeyIsDown(vkSmallL) then
  begin
    Trike.Front.BaseAngle := Trike.Front.BaseAngle - 5; 
    if Trike.Front.BaseAngle < -50 then Trike.Front.BaseAngle := -50;
  end else
    Trike.Front.BaseAngle := Trike.Front.BaseAngle * 0.95;
    
  if KeyIsDown(vkSmallI) or KeyIsDown(vkSmallK) then
    Trike.Move(KeyIsDown(vkSmallI),KeyIsDown(vkSmallK))
  else
    Trike.Stop;
  
  // trike
  glPushMatrix;
  Trike.Draw(Fps.Time/1000);
  glPopMatrix;
  
  // oak1
//  Oak1.Position.X := 150;
//  Oak1.Position.Y := 400;

//  glFrontFace(GL_CCW);
//  glEnable(GL_TEXTURE_2D);
//glDisable(GL_CULL_FACE);
//glShadeModel(GL_FLAT);

  if ShowTrees then
  begin
    ShowTreeBillboards;
    ShowTreeModels;
  end;  

  ShowGrassBillboards;


  // print FPS
  Fps.Tic;
  Font.OutTextXY(10,10,'FPS: '+inttostr(Fps.Effective));
  
  // refresh
  glutSwapBuffers;
end;

procedure OnIdle; cdecl;
begin
  glutPostRedisplay;
end;

begin
  SplashMax := 5;
  //Debug.Enable(dlInput);
  //Debug.Enable(dlBegin);
  InitOpenGl;
  Fps.Limit := 100;

  // callbacks
  glutPassiveMotionFunc(@GroundPassiveMotion);
  glutDisplayFunc(@OnDisplay);
  glutIdleFunc(@OnIdle);

  // main loop
  try
    glutMainLoop;
  except
    on E:ExGlutTerminate do
      writeln(E.Message);
  end;
end.
